// Copyright 2011 Google Inc. All Rights Reserved.
// Author: jacobsa@google.com (Aaron Jacobs)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <iostream>

#include <gflags/gflags.h>
#include <glog/logging.h>
#include <v8.h>

#include "base/macros.h"
#include "base/stl_decl.h"
#include "file/file_utils.h"
#include "proto/named_scripts.pb.h"
#include "shell/invoke_command.h"
#include "v8/v8_utils.h"

DEFINE_string(data_dir, "",
              "Path to directory containing built-in flame data.");

using flame::ConvertToString;
using flame::DescribeError;
using flame::ExecuteJs;
using flame::NamedScript;
using flame::NamedScripts;
using flame::RegisterFunction;

using v8::Array;
using v8::Context;
using v8::Handle;
using v8::Function;
using v8::HandleScope;
using v8::Local;
using v8::ObjectTemplate;
using v8::Persistent;
using v8::String;
using v8::TryCatch;
using v8::Value;

static Handle<Value> WriteToStdout(
    const v8::Arguments& args) {
  CHECK_EQ(1, args.Length());
  const string message = ConvertToString(args[0]);
  std::cout << message;
  return v8::Undefined();
}

static Handle<Value> RunScriptFile(
    const v8::Arguments& args) {
  CHECK_EQ(1, args.Length());
  const string script_file = ConvertToString(args[0]);

  // TOOD(jacobsa): Handle non-existent or unreadable files.
  const string contents = ReadFileOrDie(script_file);
  return ExecuteJs(contents, script_file);
}

static Handle<Value> InvokeCommandJs(
    const v8::Arguments& args) {
  CHECK_EQ(1, args.Length());
  const string command = ConvertToString(args[0]);

  string stdout_contents;
  string stderr_contents;
  const uint8 exit_code =
      flame::InvokeCommand(
          command,
          &stdout_contents,
          &stderr_contents);

  const Local<v8::Object> result = v8::Object::New();
  CHECK(
      result->Set(
          v8::String::New("exitCode"),
          v8::Integer::NewFromUnsigned(exit_code)));

  CHECK(
      result->Set(
          v8::String::New("stdout"),
          v8::String::New(stdout_contents.data(), stdout_contents.size())));

  CHECK(
      result->Set(
          v8::String::New("stderr"),
          v8::String::New(stderr_contents.data(), stderr_contents.size())));

  return result;
}

// Get a reference to the function of the supplied name.
static Local<Function> GetFunctionNamed(const string& name) {
  const Local<Value> result = ExecuteJs(name, "");
  CHECK(!result.IsEmpty());
  CHECK(result->IsFunction());

  return Local<Function>::Cast(result);
}

int main(int argc, char** argv) {
  google::InitGoogleLogging(argv[0]);
  google::ParseCommandLineFlags(&argc, &argv, true);

  // Take ownership of all handles created.
  const HandleScope handle_owner;

  // Create an appropriate global object.
  Handle<ObjectTemplate> global = ObjectTemplate::New();

  RegisterFunction(
      "__flame_invokeCommand",
      NewPermanentCallback(&InvokeCommandJs),
      &global);

  RegisterFunction(
      "__flame_runScriptFile",
      NewPermanentCallback(&RunScriptFile),
      &global);

  RegisterFunction(
      "__flame_writeToStdout",
      NewPermanentCallback(&WriteToStdout),
      &global);

  // Create a context in which to run scripts and ensure that it's used whenever
  // a context is needed below. Use our global object from above to get access
  // to the exported functions.
  const Persistent<Context> context = Context::New(NULL, global);
  const Context::Scope context_scope(context);

  // Load the built-in scripts.
  NamedScripts scripts;
  const string scripts_path =
      FLAGS_data_dir + "/" + "process_user_request.binarypb";

  if (!scripts.ParseFromString(ReadFileOrDie(scripts_path))) {
    LOG(ERROR) << "Could not parse file: " << scripts_path;
    return 1;
  }

  // Run each built-in script.
  for (uint32 i = 0; i < scripts.script_size(); ++i) {
    const NamedScript& script = scripts.script(i);

    TryCatch try_catch;
    const Local<Value> result = ExecuteJs(script.source(), script.name());
    if (result.IsEmpty()) {
      LOG(ERROR) << DescribeError(try_catch);
      return 1;
    }
  }

  // Set up global functions.
  {
    TryCatch try_catch;
    const Local<Value> result =
        flame::ExecuteJs(
            "flame.internal.runScriptFile = __flame_runScriptFile;\n"
                "flame.internal.invokeCommand = __flame_invokeCommand;\n"
                "flame.internal.writeToStdout = __flame_writeToStdout;\n",
            "");

    if (result.IsEmpty()) {
      LOG(ERROR) << DescribeError(try_catch);
      return 1;
    }
  }

  // Make a list of targets to process.
  const Local<Array> targets = Array::New();
  for (uint32 i = 1; i < argc; ++i) {
    targets->Set(i - 1, String::New(argv[i]));
  }

  // Process the requested actions.
  {
    const Local<Function> process_user_request =
        GetFunctionNamed("flame.internal.processUserRequest");

    Handle<Value> args[] = { targets };

    TryCatch try_catch;
    const Local<Value> result =
        process_user_request->Call(
            context->Global(),
            arraysize(args),
            args);

    if (result.IsEmpty()) {
      LOG(ERROR) << DescribeError(try_catch);
      return 1;
    }
  }

  return 0;
}
